#include <iostream>
#include <string.h>
#include <fstream>
#include "src/parallel/IminusAB.h"
#include "src/ManagerLss/ManagerLss.h"
#include "src/WorkerLss/WorkerLss.h"
#include "src/utils.h"

using namespace cxsc;
using namespace std;

int main(int argc, char *argv[]){
	int rank = 0, worldCommSize = 0;
	
	if(MPI_Init(&argc, &argv) != MPI_SUCCESS){
		cerr << "Erro ao executar MPI_Init" << endl;
		MPI_Abort(MPI_COMM_WORLD, -1);
	}
	
	if(MPI_Comm_rank(MPI_COMM_WORLD, &rank) != MPI_SUCCESS){
		cerr << "Erro ao executar MPI_Comm_rank" << endl;
		MPI_Abort(MPI_COMM_WORLD, -1);
	}
	
	if(MPI_Comm_size(MPI_COMM_WORLD, &worldCommSize) != MPI_SUCCESS){
		cerr << "Erro ao executar MPI_Comm_size" << endl;
		MPI_Abort(MPI_COMM_WORLD, -1);
	}
	
	int numThreads = 0;
	
	if(argc >= 3){
		numThreads = atoi(argv[2]);
	}
	
	if(rank == MASTER){
		bool userInput = false;
		
		if(argc < 3){
			cerr << "Uso: " << argv[0] << " <ordem do sistema> <num_threads>" << endl;
			MPI_Abort(MPI_COMM_WORLD, -2);
		}
		
		if(argc == 4){
			if(!strcmp(argv[2], "i"))
				userInput = true;
			else
				userInput = false;
		}
		
		int  Err;
		int m = atoi(argv[1]);
		double mmc = 5342931457063200.0; // mmc para hilbert de ordem 20
		
		cout << SetPrecision(1, 1);// << Scientific;   // Output format
		
		rmatrix A(m,m);   // Dynamic allocation
		rvector b(m);
		ivector x(m),c(m);
		
		if(userInput){
			for (int i = 1; i <= m; i++){
				for (int j = 1; j <= m; j++){
					cin >> A[i][j];
				}
			}
			
			for(int i = 1; i <= m; i++){
				cin >> b[i];
			}
		} else {
			for(int i = 1; i <= m; i++)
				for (int j = 1; j <= m; j++)
					A[i][j] = mmc / (i + j - 1);	// multiplica o numerador (1) pelo mmc entre todos os denominadores da matriz, antes de
													// dividir. Assim, diminue erro de exatidão.
			b[1] = mmc;
			for(int i = 2; i <= m; i++)
				b[i] = 0;
		}
		
		ManagerLss solverLssManager(A, b, x, rank, worldCommSize, numThreads);
		
		struct timeval inicio, fim;
		double tempo = 0.0;
	
		char arqName[100] = "result";
		strcat(arqName, argv[1]);
		fstream result(arqName, ios::out);
	
		cout << SetPrecision(18, 15) << Scientific;   // Output format
		result << SetPrecision(18, 15) << Scientific;   // Output format	
		
		gettimeofday(&inicio, NULL);
		
		Err = solverLssManager.startLss();
		
		gettimeofday(&fim, NULL);
		
		tempo = convert_time(inicio, fim);

		if (Err == 0){
			result << "Verified solution found in:" << endl << x << endl;
			cerr << "Verified solution found. Written to file " << arqName << endl;
			cerr << "The result is output only once." << endl;
		} else if (Err == 1) {
			result << "Bad Condition" << endl;
			cerr << "Bad Condition... Aborting" << endl;
		} else if (Err == 2) {
			result << "Probably A is singular" << endl;
			cerr << "Probably A is singular... Aborting" << endl;
		}
				
		cout << "Tempo: " << tempo << " s" << endl;
	
		result.close();
		
	} else { // rank != MASTER
		WorkerLss solverLssWorker(rank, numThreads, true);
		
		solverLssWorker.startLss();
	}
	
	MPI_Finalize();

	return 0;
}

